package com.tcs.tsrm.beans;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;

public class PreProcessData {
	public static double SD;
	public static double[] SPILITTINGLIMITS;
	int motifWidth = 0;
	int reducedDimLen = 0;
	double deviationFilterThreshold = 0.0;
	int sensorIndex;

	/**
	 * @param motifWidthP
	 * @param reducedDimLenP
	 * @param deviationFilterThresholdP
	 * @param sensorIndexP
	 */
	public void setDataPreProcessingConstants(int motifWidthP,
			int reducedDimLenP, double deviationFilterThresholdP,
			int sensorIndexP) {
		this.motifWidth = motifWidthP;
		this.reducedDimLen = reducedDimLenP;
		this.deviationFilterThreshold = deviationFilterThresholdP;
		this.sensorIndex = sensorIndexP;
	}

	public ArrayList<double[]> getNonTrivialMatrix(String fileName,
			ClusteringThresholds thresholds) throws IOException {
		ArrayList<ArrayList<Double>> allTimeSeries = new ArrayList<ArrayList<Double>>();
		allTimeSeries = loadSensorData(fileName, sensorIndex);
		double meanWholeTS = getMean(allTimeSeries);
		double stdDevWholeTS = getStdDev(allTimeSeries, meanWholeTS);
		SD = stdDevWholeTS;
		String[] stSplittingLimits = thresholds.getSplittingLimits().split(",");
		double[] splittingLimits = new double[stSplittingLimits.length];
		for (int i = 0; i < stSplittingLimits.length; i++)
			splittingLimits[i] = Double.valueOf(stSplittingLimits[i]);
		// origSeq, 1, 2, ..., w
		SPILITTINGLIMITS = splittingLimits;
		
		// /Adding new code here
		
		ArrayList<ArrayList<Double>> allNewTimeSeries = new ArrayList<ArrayList<Double>>();
		allNewTimeSeries = zScoreNormalisation(allTimeSeries, meanWholeTS,
				stdDevWholeTS);
		double[][] matrix = createMatrix(allNewTimeSeries, motifWidth);
		double[][] originalMatrix = createMatrix(allTimeSeries, motifWidth);
		// seqAfterDevFilter, origSeq, 1, 2, ..., w
		ArrayList<double[]> normalizedMatrix = devFilterAndNormalization(
				matrix, deviationFilterThreshold);

		ArrayList<double[]> reducedMatrix = dimensionalReduction(
				normalizedMatrix, reducedDimLen, motifWidth);

		ArrayList<double[]> nonTrivialMatrix = trivialMatchRemoval(
				reducedMatrix, stdDevWholeTS, splittingLimits, thresholds);
		setClusterStaticParams(matrix, nonTrivialMatrix);
		setClusterStaticParams2(originalMatrix, nonTrivialMatrix);
		return nonTrivialMatrix;
	}

		
	
	/**
	 * @param matrix
	 * @param nonTrivialMatrix
	 */
	private void setClusterStaticParams(double[][] matrix,
			ArrayList<double[]> nonTrivialMatrix) {
		Cluster.originalMatrix = matrix;
		HashMap<Integer, double[]> originalMatHash = new HashMap<Integer, double[]>();

		for (double[] line : matrix) {
			double[] aLine = new double[line.length];
			for (int i = 0; i < line.length; i++) {
				aLine[i] = line[i];
			}
			originalMatHash.put((int) line[0], aLine);
		}

		HashMap<Integer, double[]> nonTrivialMatHash = new HashMap<Integer, double[]>();
		for (double[] line : nonTrivialMatrix) {
			double[] aLine = new double[line.length - 2];
			for (int i = 0; i < aLine.length; i++) {
				aLine[i] = line[i + 2];
			}
			nonTrivialMatHash.put((int) line[0], aLine);
		}
		Cluster.originalMatrixHashMap = originalMatHash;
		Cluster.nonTrivialMatrix = nonTrivialMatrix;
		Cluster.nonTrivialMatrixHashMap = nonTrivialMatHash;

	}
	
	/**
	 * @param matrix
	 * @param nonTrivialMatrix
	 */
	private void setClusterStaticParams2(double[][] matrix,
			ArrayList<double[]> nonTrivialMatrix) {
		Cluster.originalMatrixWithoutNormalisation = matrix;
		HashMap<Integer, double[]> originalMatHash = new HashMap<Integer, double[]>();

		for (double[] line : matrix) {
			double[] aLine = new double[line.length];
			for (int i = 0; i < line.length; i++) {
				aLine[i] = line[i];
			}
			originalMatHash.put((int) line[0], aLine);
		}
		Cluster.originalMatrixWithoutNormalisationHashMap = originalMatHash;
	}

	

	/**
	 * @param fileName
	 * @param sensorIndex
	 * @return
	 * @throws IOException
	 */
	private ArrayList<ArrayList<Double>> loadSensorData(String fileName,
			int sensorIndex) throws IOException {
		File folder = new File(fileName); //$NON-NLS-1$
		File[] listOfFiles = folder.listFiles();
		List<File> assetList = Arrays.asList(listOfFiles);
		ArrayList<ArrayList<Double>> allTimeSeries = new ArrayList<ArrayList<Double>>();
		try {
			Collections.sort(assetList);
			for (File file : assetList) {
				if (file.isFile()) {
					ArrayList<Double> aSeries = new ArrayList<Double>();
					FileInputStream fstream;
					fstream = new FileInputStream(file);
					DataInputStream in = new DataInputStream(fstream);
					BufferedReader br = new BufferedReader(
							new InputStreamReader(in));
					String strLine;
					while ((strLine = br.readLine()) != null) {
						String[] data = strLine.split(",");
						if (!data[sensorIndex].trim().equals("")) {
							aSeries.add(Double.parseDouble(data[sensorIndex]));
						}
					}
					in.close();
					allTimeSeries.add(aSeries);
				}
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (NumberFormatException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return allTimeSeries;
	}

	/**
	 * @param point
	 * @return
	 */
	public String getCoordinates(double[] point) {
		DecimalFormat df = new DecimalFormat("0.00");
		String memberList = "";
		for (int i = 0; i < point.length; i++) {
			memberList += df.format(point[i]) + ",";
		}
		return memberList;
	}

	/**
	 * @param reducedMatrix
	 * @param sD
	 * @param splittingLimits
	 * @param thresholds
	 * @return
	 * @throws IOException
	 */
	private ArrayList<double[]> trivialMatchRemoval(
			ArrayList<double[]> reducedMatrix, double sD,
			double[] splittingLimits, ClusteringThresholds thresholds)
			throws IOException {
		ArrayList<double[]> reducedMatrixCopy = new ArrayList<double[]>();

		for (double[] row : reducedMatrix) {
			reducedMatrixCopy.add(row);
		}
		ArrayList<double[]> characterisedMatrix = characterConversion(
				reducedMatrix, splittingLimits, sD);
		ArrayList<double[]> nonTrivialMatrix = remRepetitiveRows(
				reducedMatrixCopy, characterisedMatrix, thresholds);
		return nonTrivialMatrix;
	}

	/**
	 * @param reducedMatrix
	 * @param characterisedMatrix
	 * @param thresholds
	 * @return
	 * @throws IOException
	 */
	private ArrayList<double[]> remRepetitiveRows(
			ArrayList<double[]> reducedMatrix,
			ArrayList<double[]> characterisedMatrix,
			ClusteringThresholds thresholds) throws IOException {
		ArrayList<double[]> filteredMatrix = new ArrayList<double[]>();
		HashMap<Integer, double[]> reducedMatHash = new HashMap<Integer, double[]>();
		double[] eachReducedMatRow = reducedMatrix.get(0);
		int key = (int) eachReducedMatRow[1];
		reducedMatHash.put(key, eachReducedMatRow);

		Stack<Integer> rowIndex = new Stack<Integer>();
		rowIndex.push(0);
		for (int i = 1; i < characterisedMatrix.size(); i++) {
			key = (int) reducedMatrix.get(i)[1];
			eachReducedMatRow = reducedMatrix.get(i);
			reducedMatHash.put(key, eachReducedMatRow);
			if (Arrays.equals(Arrays.copyOfRange(characterisedMatrix.get(i), 2,
					characterisedMatrix.get(i).length), Arrays.copyOfRange(
					characterisedMatrix.get(rowIndex.peek()), 2,
					characterisedMatrix.get(i).length))) {
				rowIndex.pop();
				rowIndex.push(i);
			} else {
				rowIndex.push(i);
			}
		}

		Iterator<Integer> i = rowIndex.iterator();
		while (i.hasNext()) {
			int index = i.next();
			filteredMatrix.add(reducedMatrix.get(index));
		}
		return filteredMatrix;
	}

	/**
	 * @param reducedMatrix
	 * @param splittingLimits
	 * @param sD
	 * @return
	 */
	private static ArrayList<double[]> characterConversion(
			ArrayList<double[]> reducedMatrix, double[] splittingLimits,
			double sD) {
		sD = 1.0;
		ArrayList<double[]> characterisedMatrix = new ArrayList<double[]>();
		for (double[] eachRow : reducedMatrix) {
			double[] newRow = new double[eachRow.length];
			newRow[0] = eachRow[0];
			newRow[1] = eachRow[1];
			for (int i = 2; i < eachRow.length; i++) {
				for (int j = 0; j < splittingLimits.length; j++) {
					if (eachRow[i] / sD < splittingLimits[j]) {
						newRow[i] = j;
						break;
					}
					if (j == splittingLimits.length - 1) {
						newRow[i] = splittingLimits.length;
					}
				}
			}
			characterisedMatrix.add(newRow);
		}
		return characterisedMatrix;
	}

	/**
	 * @param normalizedMatrix
	 * @param d
	 * @param w
	 * @return
	 */
	private ArrayList<double[]> dimensionalReduction(
			ArrayList<double[]> normalizedMatrix, int d, int w) {
		int reductionFactor = w / d;

		ArrayList<double[]> reducedMatrix = new ArrayList<double[]>();
		double sum = 0;
		int counter;

		for (double[] eachRow : normalizedMatrix) {
			double[] newRow = new double[(d + 2)];
			counter = 0;
			newRow[counter++] = eachRow[0];
			newRow[counter++] = eachRow[1];
			for (int i = 2; i < eachRow.length; i += reductionFactor) {
				sum = 0;
				for (int j = i; j < i + reductionFactor; j++) {
					sum += eachRow[j];
				}
				newRow[counter++] = sum / reductionFactor;
			}
			reducedMatrix.add(newRow);
		}
		return reducedMatrix;

	}

	/**
	 * @param allTimeSeries
	 * @param mean
	 * @return
	 */
	private double getStdDev(ArrayList<ArrayList<Double>> allTimeSeries,
			double mean) {
		double variance = getVariance(mean, allTimeSeries);
		return (Math.sqrt(variance));
	}

	private double getVariance(double mean,
			ArrayList<ArrayList<Double>> allTimeSeries) {

		int noOfPoints = 0;
		double deviations = 0;
		for (ArrayList<Double> aSeries : allTimeSeries) {
			noOfPoints += aSeries.size();
			for (double d : aSeries) {
				deviations += Math.pow((d - mean), 2);
			}
		}
		return deviations / ((double) noOfPoints);
	}

	/**
	 * @param allTimeSeries
	 * @return
	 */
	private ArrayList<ArrayList<Double>> zScoreNormalisation(
			ArrayList<ArrayList<Double>> allNewTimeSeries, double mean,
			double standarddeviation) {
		double standardValue = 0;
		ArrayList<ArrayList<Double>> TimeSeries = new ArrayList<ArrayList<Double>>();
		for (ArrayList<Double> aSeries : allNewTimeSeries) {
			ArrayList<Double> newSeries = new ArrayList<Double>();
			for (double d : aSeries) {
				standardValue = ((d - mean) / standarddeviation);
				newSeries.add(standardValue);
			}
			TimeSeries.add(newSeries);
		}
		return TimeSeries;
	}

	/**
	 * @param allTimeSeries
	 * @return
	 */
	private double getMean(ArrayList<ArrayList<Double>> allTimeSeries) {

		double sum = 0;
		int noOfPoints = 0;
		for (ArrayList<Double> aSeries : allTimeSeries) {
			for (double d : aSeries) {
				sum += d;
				noOfPoints++;
			}
		}

		return (sum / noOfPoints);
	}

	/**
	 * @param matrix
	 * @param t1
	 * @return
	 * @throws IOException
	 */
	private ArrayList<double[]> devFilterAndNormalization(double[][] matrix,
			double t1) throws IOException {

		ArrayList<double[]> matrixWithFilteredRows = new ArrayList<double[]>();
		int counter = 0;
		double[] aRow = new double[matrix[0].length];
		for (int i = 0; i < matrix.length; i++) {
			double max = matrix[i][1];
			double min = matrix[i][1];

			for (int j = 1; j < matrix[i].length; j++) {
				if (matrix[i][j] > max)
					max = matrix[i][j];
				else if (matrix[i][j] < min)
					min = matrix[i][j];
			}
			if ((max - min) > t1) {
				aRow = matrix[i];
				matrixWithFilteredRows.add(normalisedRow(aRow, counter++));
			}

		}
		return matrixWithFilteredRows;
	}

	/**
	 * @param aRow
	 * @param rowIndex
	 * @return
	 */
	private double[] normalisedRow(double[] aRow, int rowIndex) {

		double mean = calculateMean(aRow);
		double[] normalisedRow = new double[aRow.length + 1];
		normalisedRow[0] = rowIndex;
		normalisedRow[1] = aRow[0];

		for (int i = 2; i < normalisedRow.length; i++) {
			normalisedRow[i] = aRow[i - 1] - mean;
		}
		return normalisedRow;
	}

	/**
	 * @param aRow
	 * @return
	 */
	private double calculateMean(double[] aRow) {
		double sum = 0;
		for (int i = 1; i < aRow.length; i++) {
			sum += aRow[i];
		}
		return sum / (aRow.length - 1);
	}

	/**
	 * @param allTimeSeries
	 * @param w
	 * @return
	 */
	private double[][] createMatrix(ArrayList<ArrayList<Double>> allTimeSeries,
			int w) {

		ArrayList<ArrayList<Double>> finalMatrix = new ArrayList<ArrayList<Double>>();

		int rowIndex = 0;
		int totalRows = 0;
		for (ArrayList<Double> eachVector : allTimeSeries) {
			finalMatrix = makeSubsequences(finalMatrix, eachVector, w, rowIndex);
			rowIndex += eachVector.size() - w + 1;
			totalRows = totalRows + eachVector.size() - w + 1;

		}

		double[][] matrix = getMatrix(finalMatrix, totalRows, w);
		return matrix;
	}

	/**
	 * @param finalMatrix
	 * @param totalRows
	 * @param w
	 * @return
	 */
	private double[][] getMatrix(ArrayList<ArrayList<Double>> finalMatrix,
			int totalRows, int w) {
		double[][] matrix = new double[totalRows][w + 1];
		ArrayList<Double> eachRow = new ArrayList<Double>();
		for (int i = 0; i < totalRows; i++) {
			eachRow = finalMatrix.get(i);
			for (int j = 0; j < w + 1; j++) {
				matrix[i][j] = eachRow.get(j);
			}
		}

		return matrix;
	}

	/**
	 * @param finalMatrix
	 * @param eachVector
	 * @param w
	 * @param rowIndex
	 * @return
	 */
	private ArrayList<ArrayList<Double>> makeSubsequences(
			ArrayList<ArrayList<Double>> finalMatrix,
			ArrayList<Double> eachVector, int w, int rowIndex) {
		int rows = eachVector.size() - w + 1;
		int k = 0;
		for (int i = 0; i < rows; i++) {
			ArrayList<Double> eachSubs = new ArrayList<Double>();
			for (int j = 0; j < w + 1; j++) {
				if (j == 0) {
					eachSubs.add((double) rowIndex++);
					continue;
				} else {
					eachSubs.add(eachVector.get(k++));
				}
			}
			finalMatrix.add(eachSubs);
			k = k - w + 1;
		}
		return finalMatrix;
	}

}
